昨天在整理 AI Engineering 的時候,我比較在意的是一件事:當 AI 的答案出錯,我到底能不能知道問題發生在哪一層。
到了今天,我想往前再走一步,因為就算現在 Prompt 已經調到看起來很穩、Demo 每次測也都能正常回答,其實離「真的可以放進產品裡給別人用」還有一段距離。
這個差距以前其實沒有那麼明顯,我自己做小專案的時候也很容易有一種錯覺,只要 API 接得起來、Prompt 寫得夠好、畫面上能吐出答案,好像功能就已經完成了。但真的把流程拆開之後才發現,Demo 成功通常只代表一件事:在我準備好的輸入下,模型剛好給出了我想要的結果。
真正上線之後,使用者不會照著我測試時的方式輸入資料。
有人會少打一個欄位、有人一次貼幾千字、有人問完全不在預期範圍裡的問題,也可能 API 突然 timeout、模型回傳格式跑掉,甚至同一個問題隔幾分鐘再問一次,答案就長得不太一樣。這些事情單看 Prompt 幾乎都解決不了。
所以今天我想實際拆一次,一個「可以 Demo 的 AI 功能」到底還缺了什麼,才有辦法慢慢變成一個真的能放進系統裡的功能。
假設今天要做一個客服分類功能,輸入一段使用者訊息,讓模型判斷它屬於:
最簡單的寫法其實很直覺。
prompt = f"""
請判斷下面這則客服訊息屬於哪一類:
帳號問題
付款問題
技術問題
其他
訊息:
{user_message}
只回答分類名稱。
"""
接著把 Prompt 丟給模型,再把模型回傳的文字顯示出來。
如果測試資料是:
我的信用卡已經扣款,但系統還是顯示尚未付款
模型回:
付款問題
看起來完全沒問題。
再測幾筆也都正常,這時候如果只是做課堂 Demo,其實已經可以收工了,但如果這段結果下一步要直接進資料庫、觸發客服分流,事情就開始不一樣。
因為系統真正需要的不是「模型看起來回答對」,而是我能不能確定接下來的程式知道該怎麼處理這個答案。
我原本叫模型「只回答分類名稱」,但這其實只是一個要求,不是一個保證。
今天模型可能回:
付款問題
明天也可能變成:
這看起來屬於付款問題。
甚至是:
分類:付款問題
原因:使用者已經被扣款,但付款狀態沒有更新。
人看得懂,程式不一定看得懂。
如果後面寫的是:
if result == "付款問題":
assign_to_payment_team()
那第二種、第三種輸出全部都會失敗。
所以第一個很明顯的差異是,Production 不能只依賴「Prompt 叫模型乖乖輸出」,還需要限制輸出格式。
例如把答案改成固定 JSON:
{
"category": "payment"
}
程式收到結果之後,再檢查 category 是不是只出現在允許的四個值裡。
allowed_categories = {
"account",
"payment",
"technical",
"other"
}
if result["category"] not in allowed_categories:
# 不直接往下執行
handle_invalid_output()
做到這一步,AI 才比較像系統裡的一個 component,而不是一個會自由回答問題的聊天室。
Demo 時我會自己準備輸入,例如:
我的信用卡已經扣款,但系統顯示付款失敗
可是上線之後可能收到:
不能用
或者:
???
甚至直接有人貼了一整篇文章進來。
如果每個輸入都直接送給 LLM,我等於把所有判斷責任全部丟給模型,但有些東西根本不需要進到模型。
所以在 Prompt 前面其實還要有一層 Input Validation。
使用者輸入
↓
輸入檢查
↓
Prompt
↓
LLM
例如最基本可以先檢查:
if not user_message.strip():
return "請輸入問題"
if len(user_message) > 2000:
return "輸入內容過長"
再往後甚至可以檢查語言、必要欄位、資料格式,或先判斷這個輸入到底是不是目前功能能處理的內容。
以前我會覺得這些跟 AI 沒什麼關係,但開始把 AI 當成產品的一部分之後,反而會發現這些普通的軟體工程工作非常重要。
因為模型本身已經是不確定的元件,如果模型前後又全部沒有規則,整個系統最後會變得很難控制。
Demo 很少特別測這件事。
API 正常、網路正常、模型正常,一路跑到底,看起來當然沒有問題。
但 Production 一定會遇到:
Timeout
Rate Limit
API Error
Invalid JSON
模型拒答
模型輸出不符合 Schema
這時候如果程式只有:
response = call_llm(prompt)
return response
任何一個環節出錯,整個功能就一起掛掉。
所以這裡開始需要一些原本在 Demo 幾乎不會注意的東西,例如 timeout、retry 和 fallback。
try:
response = call_llm(
prompt,
timeout=10
)
except TimeoutError:
return fallback_response()
Retry 也不是無限重試,而是可能只允許一兩次,不然一個使用者請求就會在背景一直燒 API 費用。
for attempt in range(2):
try:
response = call_llm(prompt)
break
except Exception:
if attempt == 1:
return fallback_response()
這些程式本身其實沒有很難,但我覺得觀念上的差別很大。
Demo 的思考方式通常是:
模型成功的時候,我可以做到什麼?
Production 要開始問的是:
模型失敗的時候,我的系統會發生什麼?
這也是我今天整理時覺得差距最大的地方。
假設客服分類每天處理 10,000 筆資料,其中 300 筆被分類錯誤,如果系統只是單純把 LLM 結果寫進資料庫,其實我可能根本不知道那 300 筆存在。
程式沒有 crash,API 也全部回 200,看起來系統運作得非常健康,但 AI 的品質其實正在下降。
所以一般 Backend 常看的東西可能是:
API 有沒有掛
Latency 多高
Error Rate 多少
CPU / Memory 是否正常
AI 系統還需要另外知道:
模型現在回答得好不好?
輸出格式錯誤多少次?
Fallback 發生多少次?
不同類型問題的準確率如何?
Prompt 改版之後有沒有退步?
這裡就開始碰到 Evaluation 和 Observability。
至少我需要把每次請求的一些資訊留下來:
{
"request_id": "abc123",
"prompt_version": "v2",
"model": "gpt-x",
"latency_ms": 820,
"category": "payment",
"fallback": false
}
如果之後真的發生問題,我才有東西可以回頭查。
不然只知道「AI 最近好像怪怪的」,其實根本沒有辦法 debug。
還有一個以前很容易忽略的問題。
假設今天覺得分類效果不好,我把:
請判斷下面的客服問題屬於哪一類
改成:
你是一位客服分類專員,請根據使用者主要問題選擇最符合的分類
測了幾筆,好像比較準,就直接把 Prompt 改掉。
但一週後發現「技術問題」突然常被分到「其他」,這時候第一個問題就是:
到底是哪一次修改造成的?
如果 Prompt 只是散落在程式碼裡的一段字串,我很難追。
所以 Prompt 本身也需要版本,例如:
PROMPT_VERSION = "customer_classification_v3"
每次 request 同時記錄使用的是哪個版本。
這樣之後才能比較:
v1 accuracy:84%
v2 accuracy:89%
v3 accuracy:82%
看到這個結果之後,至少我知道 v3 可能不是升級,而是 regression。
這件事其實跟一般軟體的版本管理很像,只是以前我們主要在管 code,現在 Prompt 和模型設定本身也變成系統行為的一部分。
如果把今天整理的東西全部放回來,原本的 Demo 可能只有:
User
↓
Prompt
↓
LLM
↓
Answer
但真的準備上線時,慢慢會變成:
User
↓
Input Validation
↓
Prompt Template
↓
LLM
↓
Structured Output
↓
Schema Validation
↓
Business Logic
↓
Response
旁邊還另外會有:
Logging
Evaluation
Retry
Fallback
Monitoring
Prompt Version
這時候 Prompt 還是很重要,但它只是整條流程中的一部分。
而我現在理解的 AI Engineering,比較接近是在處理這整條路,而不只是把中間那個 Prompt 寫得更漂亮。
我以前做 AI 功能時,很容易把「模型有回答」當成「功能完成」。
但今天把流程拆開後,我覺得比較合理的判斷方式應該是:
Demo:
正常輸入進來時,AI 能不能完成任務?
Production:
輸入亂掉、模型亂掉、API 掛掉、輸出格式改變時,
整個系統還能不能正常處理?
真正麻煩的通常不是那一次成功的回答,而是剩下那些沒有按照預期發生的情況。
所以今天沒有特別去追更複雜的 Agent 或 RAG,而是先把一個最普通的 LLM 功能往 Production 推一步,補上輸入檢查、Structured Output、Schema Validation、Retry、Fallback 和 Logging。
這幾個東西單獨看都很普通,但接起來之後,AI 才開始從「可以玩的 Demo」變成一個真的能被系統使用的元件。
明天我想繼續往這個問題走,實際處理另一個更麻煩的地方:
AI 的答案沒有報錯,但答案其實是錯的,我要怎麼自動發現?